package com.golxen.framework.netty.session.agent;


import com.golxen.framework.PBPacket.PBPacket;
import com.golxen.framework.common.server.eServerType;
import com.golxen.framework.common.server.game.dao.entity.ProcessInfo;
import com.golxen.framework.netty.NettyServices;
import com.golxen.framework.netty.ServerClientChannelInitializer;
import com.golxen.framework.netty.session.base.AbstractSession;
import com.golxen.framework.netty.session.base.NettyChannelSession;
import com.golxen.framework.netty.session.base.ServerLinkedGroup;
import com.golxen.utils.LogUtil;
import com.golxen.utils.YmlUtil;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import org.slf4j.Logger;

import java.net.InetSocketAddress;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ServerClientGroup extends ServerLinkedGroup {

	private static Logger logger = LogUtil.COMMON;

	protected InetSocketAddress addr;
	protected PBPacket firstMsg;
	Map<Integer, ServerClientEventLoopGroup> eventLoopGroupEntityMap;

	public ServerClientGroup(InetSocketAddress addr, PBPacket firstMsg, int size,
							  eServerType serverType, ProcessInfo info) {
		super(size, YmlUtil.getInt("netty.agent.serverIndex"), info.getPort(), info.getAddress(),
				serverType);
		this.addr = addr;
		this.firstMsg = firstMsg;
		eventLoopGroupEntityMap = new ConcurrentHashMap<>();
	}

	public ServerClientGroup(InetSocketAddress addr, PBPacket firstMsg, int size,
							 eServerType serverType) {
		super(size, YmlUtil.getInt("netty.agent.serverIndex"), YmlUtil.getInt("netty.game.port"), YmlUtil.getString("netty.game.address"),
				serverType);
		this.addr = addr;
		this.firstMsg = firstMsg;
		eventLoopGroupEntityMap = new ConcurrentHashMap<>();
	}


	public ChannelHandler createChannelHandler() {
		return new ServerClientHandler();
	}

	@Override
	public NettyChannelSession connect(int i) throws Exception {
		if (!eventLoopGroupEntityMap.containsKey(i)) {
			eventLoopGroupEntityMap.put(i, new ServerClientEventLoopGroup());
		}
		ServerClientEventLoopGroup entity = eventLoopGroupEntityMap.get(i);
		try {
			if (entity.getGroup() != null && entity.getFuture() != null) {
				if (entity.getFuture().channel().isActive()) {
					entity.getFuture().channel().close();
				}
				entity.getGroup().shutdownGracefully();
			}
		} catch (Exception ex) {
			logger.error("connect错误", ex);
		}

		if (Epoll.isAvailable()) {
			entity.setGroup(new EpollEventLoopGroup(Runtime.getRuntime().availableProcessors()));
		} else {
			entity.setGroup(new NioEventLoopGroup(Runtime.getRuntime().availableProcessors()));
		}

		EventLoopGroup workerGroup = entity.getGroup();

		Bootstrap bootstrap = new Bootstrap();
		bootstrap.group(workerGroup);

		bootstrap.channel(Epoll.isAvailable() ? EpollSocketChannel.class : NioSocketChannel.class);
		bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
		bootstrap.option(ChannelOption.TCP_NODELAY, true);
		bootstrap.option(ChannelOption.SO_RCVBUF, 1024 * 32);
		bootstrap.option(ChannelOption.SO_SNDBUF, 1024 * 32);
		bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 300);
		bootstrap.handler(
				new ServerClientChannelInitializer(serverType + "-1", createChannelHandler()));
		try {
			ChannelFuture future = bootstrap.connect(addr).syncUninterruptibly();
			entity.setFuture(future);
			if (future.isSuccess() && future.channel().isActive()) {
				NettyChannelSession socketSession = this.createSession(future.channel(), i, addr);
				socketSession.setId(i);
				socketSession.write(firstMsg);
				return socketSession;
			} else {
				logger.error("isSuccess : " + future.isSuccess() + ", isActive : " + future.channel().isActive());
			}
		} catch (Exception ex) {
			workerGroup.shutdownGracefully();
			throw ex;
		}

		return null;
	}

	@Override
	protected NettyChannelSession createSession(Channel channel, int index, InetSocketAddress addr) {
		NettyChannelSession session = new NettyChannelSession(channel, index);
		sessions[index] = session;
		return session;
	}

	public void setSessionCheckTime(int index) {
		AbstractSession session = sessions[index];
		session.setCrossCheckAliveTime(System.currentTimeMillis());

//		LogUtils.COMMON.info("==={}/{}/{} sessions= {}", index, serverType, id, sessions);
//		LogUtils.COMMON.info("==={}/{}/{} {} set check time time={}", index, serverType, id, session,
//				session.getCrossCheckAliveTime());
	}

	@Override
	public void keep() {
		int size = (sessions != null) ? sessions.length : 0;
		long nowTime = System.currentTimeMillis();
		for (int i = 0; i < size; i++) {

			synchronized (this) {
				AbstractSession session = sessions[i];
				if (getServerType() == eServerType.CROSS || getServerType() == eServerType.CROSS2) {
					// 超过一分钟没收到消息
					if (isAlive(session) && nowTime < session.getCrossCheckAliveTime() + 2 * 60 * 1000) {
						continue;
					}
					if (null != session) {
						LogUtil.COMMON.info("===={} keep serverType={} id={} index={} time={} now-time={}", session,
								serverType, serverId, i, session.getCrossCheckAliveTime(),
								nowTime - session.getCrossCheckAliveTime());
					}
				} else if (isAlive(session)) {
					continue;
				}

				try {
					NettyChannelSession session0 = connect(i);
					if (session0 == null) {
						break;
					}
					session0.setCrossCheckAliveTime(System.currentTimeMillis());
					sessions[i] = session0;
					onReconnSucc(session0);
				} catch (Exception e) {
					logger.error("socket index=" + i + " addr=" + addr, e);
					break;
				}
			}

		}
	}

	public void onReconnSucc(NettyChannelSession session) {
		int linkIndex = session.getLinkIndex();
		ServerClientMgr.sendRegisterServer(session, linkIndex, getServerType());
		session.getChannel().attr(NettyServices.LINKED_CLIENT).set(this);
	}

	public void send(PBPacket packet) {
		write(packet.getPlayerId(), packet);
	}

	public void send(int userId, PBPacket packet) {
		write(userId, packet);
	}

	public void writeToAllSession(PBPacket msg) {
		writeToAll(msg);
	}

	public boolean reConnect() {
		keep();

		List<NettyChannelSession> sessions = getAll();
		for (NettyChannelSession socketSession : sessions) {
			if (socketSession == null || !socketSession.isAlive()) {
				return false;
			}
		}

		return true;
	}

	@Override
	public String toString() {
		return "[id : " + serverId + " , adress : " + address + "  ,  type : " + serverType
				+ " , port : " + port + "] " + super.toString();
	}

}
